package com.ilikesoup.instant.compare;

import com.google.common.collect.Lists;
import com.ilikesoup.instant.inclusion.Inclusion;
import com.ilikesoup.instant.inclusion.Inclusions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Predicate;

public class ComparePair<L, R, V> {

    private static final Logger logger = LoggerFactory.getLogger(ComparePair.class);

    private final String name;

    private final Function<L, V> left;

    private final Function<R, V> right;

    private final BiPredicate<V, V> comparer;

    private ComparePair(Function<L, V> left, Function<R, V> right) {
        this(null ,left, right);
    }

    private ComparePair(String name, Function<L, V> left, Function<R, V> right) {
        this(name ,left, right, null);
    }

    private ComparePair(String name, Function<L, V> left, Function<R, V> right, BiPredicate<V, V> comparer) {
        this.name = Optional.ofNullable(name).orElse(getDefaultName());
        this.left = Objects.requireNonNull(left);
        this.right = Objects.requireNonNull(right);
        this.comparer = comparer;
    }

    protected String getDefaultName() {
        return UUID.randomUUID().toString();
    }

    public Function<L, V> getLeft() {
        return left;
    }

    public Function<R, V> getRight() {
        return right;
    }

    public BiPredicate<V, V> getComparer() {
        return comparer;
    }

    public String getName() {
        return name;
    }

    public ComparePair<L, R, V> copyOf(BiPredicate<V, V> comparer) {
        if(comparer == this.comparer) {
            return this;
        }
        return new ComparePair<>(name, left, right, comparer);
    }

    public static <L, V> ComparePair<L, L, V> ofEquals(Function<L, V> same) {
        return new ComparePair<>(null, same, same, Objects::equals);
    }

    public static <L, V> ComparePair<L, L, V> ofEquals(String name, Function<L, V> same) {
        return new ComparePair<>(name, same, same, Objects::equals);
    }

    public static <L, V> ComparePair<L, L, V> of(Function<L, V> same) {
        return new ComparePair<>(same, same);
    }

    public static <L, V> ComparePair<L, L, V> of(String name, Function<L, V> same) {
        return new ComparePair<>(name, same, same);
    }

    public static <A, V> ComparePair<A, A, V> of(Function<A, V> same, BiPredicate<V, V> comparer) {
        return new ComparePair<>(null, same, same, comparer);
    }

    public static <A, V> ComparePair<A, A, V> of(String name, Function<A, V> same, BiPredicate<V, V> comparer) {
        return new ComparePair<>(name, same, same, comparer);
    }

    public static <A, R, V> ComparePair<A, R, V> of(Function<A, V> first, Function<R, V> second) {
        return new ComparePair<>(first, second);
    }

    public static <A, R, V> ComparePair<A, R, V> of(String name, Function<A, V> first, Function<R, V> second) {
        return new ComparePair<>(name, first, second);
    }

    public static <A, R, V> ComparePair<A, R, V> of(Function<A, V> first, Function<R, V> second, BiPredicate<V, V> comparer) {
        return new ComparePair<>(null, first, second, comparer);
    }

    public static <A, R, V> ComparePair<A, R, V> of(String name, Function<A, V> first, Function<R, V> second, BiPredicate<V, V> comparer) {
        return new ComparePair<>(name, first, second, comparer);
    }

    public static <L> List<ComparePair<L, L, ?>> ofBeanEquals(Class<L> clazz, Inclusion<String> fieldNameInclusion) {
        return ofBeanEquals(clazz, Inclusions.predicate(fieldNameInclusion, String::equals));
    }

    public static <L> List<ComparePair<L, L, ?>> ofBeanEquals(Class<L> clazz, Predicate<String> fieldNamePredicate) {
        try {
            List<ComparePair<L, L, ?>> result = Lists.newArrayList();
            BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
                if(fieldNamePredicate.test(propertyDescriptor.getName())) {
                    result.add(createBeanPropertyComparePair(propertyDescriptor));
                }
            }
            return result;
        } catch (IntrospectionException e) {
            logger.error("创建bean比较对象失败：{}", e);
        }
        return null;
    }

    private static <L, V> ComparePair<L, L, V> createBeanPropertyComparePair(PropertyDescriptor propertyDescriptor) {
        return ofEquals(propertyDescriptor.getName(), l -> {
            try {
                Method readMethod = propertyDescriptor.getReadMethod();
                if(!readMethod.isAccessible()) {
                    readMethod.setAccessible(true);
                }
                return (V) propertyDescriptor.getReadMethod().invoke(l);
            } catch (Exception e) {
                logger.error("方法调用失败：{}", e);
                return null;
            }
        });
    }

}
